! Copyright (c) 2013,  Los Alamos National Security, LLC (LANS)
! and the University Corporation for Atmospheric Research (UCAR).
!
! Unless noted otherwise source code is licensed under the BSD license.
! Additional copyright and license information can be found in the LICENSE file
! distributed with this code, or at http://mpas-dev.github.com/license.html
!
!***********************************************************************
!
!  mpas_sort
!
!> \brief   MPAS Sort and search module
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This module provides routines for various sorting methods, in addition to a binary search.
!
!-----------------------------------------------------------------------

module mpas_sort

   use mpas_kind_types
   use mpas_derived_types
   use mpas_log

   interface mpas_quicksort
      module procedure mpas_quicksort_1dint
      module procedure mpas_quicksort_1dreal
      module procedure mpas_quicksort_2dint
      module procedure mpas_quicksort_2dreal
   end interface

   contains

!***********************************************************************
!
!  recursive routine mpas_mergesort
!
!> \brief   MPAS Merge sort
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine recursively calls itself to perform a merge sort on array.
!
!-----------------------------------------------------------------------
   recursive subroutine mpas_mergesort(array, d1, n1, n2)!{{{
   
      implicit none
   
      ! Arguments
      integer, intent(in) :: d1 !< Input: Size of first dimension of array
      integer, intent(in) :: n1 !< Input: Beginning of second dimension of array
      integer, intent(in) :: n2 !< Input: Ending of second dimension of array
      integer, dimension(1:d1,n1:n2), intent(inout) :: array !< Input/Output: Array to be sorted (in-place)
   
      ! Local variables
      integer :: i, j, k
      integer :: rtemp
      integer, dimension(1:d1,1:n2-n1+1) :: temp
   
      if (n1 >= n2) return
   
      if (n2 - n1 == 1) then
        if (array(1,n1) > array(1,n2)) then
           do i=1,d1
              rtemp = array(i,n1)
              array(i,n1) = array(i,n2)
              array(i,n2) = rtemp
           end do
        end if
        return
      end if
   
      call mpas_mergesort(array(1:d1,n1:n1+(n2-n1+1)/2), d1, n1, n1+(n2-n1+1)/2)
      call mpas_mergesort(array(1:d1,n1+((n2-n1+1)/2)+1:n2), d1, n1+((n2-n1+1)/2)+1, n2)
   
      i = n1
      j = n1 + ((n2-n1+1)/2) + 1
      k = 1
      do while (i <= n1+(n2-n1+1)/2 .and. j <= n2)
        if (array(1,i) < array(1,j)) then
          temp(1:d1,k) = array(1:d1,i)
          k = k + 1
          i = i + 1
        else
          temp(1:d1,k) = array(1:d1,j)
          k = k + 1
          j = j + 1
        end if
      end do
   
      if (i <= n1+(n2-n1+1)/2) then
        do while (i <= n1+(n2-n1+1)/2)
          temp(1:d1,k) = array(1:d1,i)
          i = i + 1
          k = k + 1
        end do
      else
        do while (j <= n2)
          temp(1:d1,k) = array(1:d1,j)
          j = j + 1
          k = k + 1
        end do
      end if
   
      array(1:d1,n1:n2) = temp(1:d1,1:k-1)
   
   end subroutine mpas_mergesort!}}}

!***********************************************************************
!
!  routine mpas_quicksort_1dint
!
!> \brief   MPAS 1D integer quicksort
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine performs a quicksort on a 1D integer array
!
!-----------------------------------------------------------------------
   subroutine mpas_quicksort_1dint(nArray, array)!{{{

      implicit none

      integer, intent(in) :: nArray !< Input: Array size
      integer, dimension(nArray), intent(inout) :: array !< Input/Output: Array to be sorted

      integer :: i, top, l, r, pivot, s
      integer :: pivot_value
      integer, dimension(1) :: temp
      integer, dimension(1000) :: lstack, rstack
      real :: rnd

      if (nArray < 1) return

      top = 1
      lstack(top) = 1
      rstack(top) = nArray

      do while (top > 0)

         l = lstack(top)
         r = rstack(top)
         top = top - 1

         call random_number(rnd)
         pivot = l + int(rnd * real(r-l))

         pivot_value = array(pivot)
         temp(1) = array(pivot)
         array(pivot) = array(r)
         array(r) = temp(1)

         s = l
         do i=l,r-1
            if (array(i) <= pivot_value) then
               temp(1) = array(s)
               array(s) = array(i)
               array(i) = temp(1)
               s = s + 1
            end if
         end do

         temp(1) = array(s)
         array(s) = array(r)
         array(r) = temp(1)

         if (s-1 > l) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = l
            rstack(top) = s-1
         end if

         if (r > s+1) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = s+1
            rstack(top) = r
         end if
      end do

   end subroutine mpas_quicksort_1dint!}}}

!***********************************************************************
!
!  routine mpas_quicksort_1dreal
!
!> \brief   MPAS 1D real quicksort
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine performs a quicksort on a 1D real array
!
!-----------------------------------------------------------------------
   subroutine mpas_quicksort_1dreal(nArray, array)!{{{

      implicit none

      integer, intent(in) :: nArray !< Input: Array size
      real (kind=RKIND), dimension(nArray), intent(inout) :: array !< Input/Output: Array to be sorted

      integer :: i, top, l, r, pivot, s
      real (kind=RKIND) :: pivot_value
      real (kind=RKIND), dimension(1) :: temp
      integer, dimension(1000) :: lstack, rstack
      real :: rnd

      if (nArray < 1) return

      top = 1
      lstack(top) = 1
      rstack(top) = nArray

      do while (top > 0)

         l = lstack(top)
         r = rstack(top)
         top = top - 1

         call random_number(rnd)
         pivot = l + int(rnd * real(r-l))

         pivot_value = array(pivot)
         temp(1) = array(pivot)
         array(pivot) = array(r)
         array(r) = temp(1)

         s = l
         do i=l,r-1
            if (array(i) <= pivot_value) then
               temp(1) = array(s)
               array(s) = array(i)
               array(i) = temp(1)
               s = s + 1
            end if
         end do

         temp(1) = array(s)
         array(s) = array(r)
         array(r) = temp(1)

         if (s-1 > l) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = l
            rstack(top) = s-1
         end if

         if (r > s+1) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = s+1
            rstack(top) = r
         end if
      end do

   end subroutine mpas_quicksort_1dreal!}}}

!***********************************************************************
!
!  routine mpas_quicksort_2dint
!
!> \brief   MPAS 2D integer quicksort
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine performs a quicksort on a 2D integer array
!
!-----------------------------------------------------------------------
   subroutine mpas_quicksort_2dint(nArray, array)!{{{

      implicit none

      integer, intent(in) :: nArray !< Input: Array size
      integer, dimension(2,nArray), intent(inout) :: array !< Input/Output: Array to be sorted

      integer :: i, top, l, r, pivot, s
      integer :: pivot_value
      integer, dimension(2) :: temp
      integer, dimension(1000) :: lstack, rstack
      real :: rnd

      if (nArray < 1) return

      top = 1
      lstack(top) = 1
      rstack(top) = nArray

      do while (top > 0)

         l = lstack(top)
         r = rstack(top)
         top = top - 1

         call random_number(rnd)
         pivot = l + int(rnd * real(r-l))

         pivot_value = array(1,pivot)
         temp(:) = array(:,pivot)
         array(:,pivot) = array(:,r)
         array(:,r) = temp(:)

         s = l
         do i=l,r-1
            if (array(1,i) <= pivot_value) then
               temp(:) = array(:,s)
               array(:,s) = array(:,i)
               array(:,i) = temp(:)
               s = s + 1
            end if
         end do

         temp(:) = array(:,s)
         array(:,s) = array(:,r)
         array(:,r) = temp(:)

         if (s-1 > l) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = l
            rstack(top) = s-1
         end if

         if (r > s+1) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = s+1
            rstack(top) = r
         end if
      end do

   end subroutine mpas_quicksort_2dint!}}}

!***********************************************************************
!
!  routine mpas_quicksort_2dreal
!
!> \brief   MPAS 2D real quicksort
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine performs a quicksort on a 2D real array
!
!-----------------------------------------------------------------------
   subroutine mpas_quicksort_2dreal(nArray, array)!{{{

      implicit none

      integer, intent(in) :: nArray !< Input: Array size
      real (kind=RKIND), dimension(2,nArray), intent(inout) :: array !< Input/Output: Array to be sorted

      integer :: i, top, l, r, pivot, s
      real (kind=RKIND) :: pivot_value
      real (kind=RKIND), dimension(2) :: temp
      integer, dimension(1000) :: lstack, rstack
      real :: rnd

      if (nArray < 1) return

      top = 1
      lstack(top) = 1
      rstack(top) = nArray

      do while (top > 0)

         l = lstack(top)
         r = rstack(top)
         top = top - 1

         call random_number(rnd)
         pivot = l + int(rnd * real(r-l))

         pivot_value = array(1,pivot)
         temp(:) = array(:,pivot)
         array(:,pivot) = array(:,r)
         array(:,r) = temp(:)

         s = l
         do i=l,r-1
            if (array(1,i) <= pivot_value) then
               temp(:) = array(:,s)
               array(:,s) = array(:,i)
               array(:,i) = temp(:)
               s = s + 1
            end if
         end do

         temp(:) = array(:,s)
         array(:,s) = array(:,r)
         array(:,r) = temp(:)

         if (s-1 > l) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = l
            rstack(top) = s-1
         end if

         if (r > s+1) then
            top = top + 1
if (top > 1000) call mpas_log_write('Quicksort exhausted its stack.', MPAS_LOG_ERR)
            lstack(top) = s+1
            rstack(top) = r
         end if
      end do

   end subroutine mpas_quicksort_2dreal!}}}

!***********************************************************************
!
!  integer function mpas_binary_search
!
!> \brief   MPAS Binary search routine
!> \author  Michael Duda
!> \date    03/27/13
!> \details 
!> This routine performs a binary search in array for the key. It either returns the index of the key within array, or n2+1 if the key is not found.
!
!-----------------------------------------------------------------------
   integer function mpas_binary_search(array, d1, n1, n2, key)!{{{

      implicit none

      integer, intent(in) :: d1, n1, n2, key
      integer, dimension(d1,n1:n2), intent(in) :: array

      integer :: l, u, k

      mpas_binary_search = n2+1

      l = n1
      u = n2
      k = (l+u)/2
      do while (u >= l)
         if (array(1,k) == key) then
            mpas_binary_search = k
            exit   
         else if (array(1,k) < key) then
            l = k + 1
            k = (l+u)/2
         else   
            u = k - 1
            k = (l+u)/2
         end if 
      end do 

   end function mpas_binary_search!}}}

end module mpas_sort
